Skip to content

Fix GH-22060 and GH-22122: pin object/closure in callback dispatch#22151

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-22060-22122-uaf-pin-fcc-object
Open

Fix GH-22060 and GH-22122: pin object/closure in callback dispatch#22151
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-22060-22122-uaf-pin-fcc-object

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@iliaal iliaal commented May 26, 2026

GH-22060 + GH-22122 fix for PHP-8.4. Same UAF in two callback-dispatch sites: zend_call_known_fcc and spl_perform_autoload forward the borrowed object/closure into the call frame without addref. 8.4 and 8.5 both need the pair, since SPL autoload still uses zend_call_known_function direct. Master only needs the zend_API change because Zend/zend_autoload.c routes through zend_call_known_fcc.

Pin object and closure across zend_call_known_fcc and
spl_perform_autoload so a callback that releases the borrowed FCC
(autoloader self-unregister, SQLite3 setAuthorizer(null)) doesn't
free $this mid-call. Initialize fcc.closure in
ReflectionFunction::invoke/invokeArgs since the pin reads it.

Fixes phpGH-22060
Fixes phpGH-22122
Copy link
Copy Markdown
Member

@DanielEScherzer DanielEScherzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay for ext/reflection, don't know about the other parts

Copy link
Copy Markdown
Member

@Girgias Girgias left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to understand why we would need to add ref the fcc.object, and my understanding is that it represents the $this value, so I guess this is valid.

However, we are once again fixing bugs and affecting performance for idiosyncratic code. I would rather prefer a solution that bans unsetting such callables within the calls. Similar to a fix we recently did for an unserialising routine in SPL.

@iliaal
Copy link
Copy Markdown
Contributor Author

iliaal commented May 29, 2026

Pinning is two refcount ops against a full call-frame setup, so the per-call cost is negligible. Banning is the bigger change: the autoload loop is deliberately built to allow add/remove mid-iteration, and disabling the authorizer mid-call is a one-shot the sqlite3 test relies on. Turning either into a thrown error is a behavior change that can't go in a stable branch. If the stricter semantics are worth having, that's a master-only follow-up.

@iliaal iliaal requested a review from Girgias May 29, 2026 21:45
@iluuu1994
Copy link
Copy Markdown
Member

However, we are once again fixing bugs and affecting performance for idiosyncratic code.

Such things will come up more and more with the raise of AI.

A while ago when fixing a similar issue, @TimWolla asked me if zend_call_function() should just take care of at least making sure $this survives. I think that's reasonable, and something that could be changed on master. In that case, we should remove immediate add/delref around zend_call_function() calls throughout the codebase.

This should target master anyway (we agreed to not fixing such bugs on stable branches that are unlikely to cause issues in the real world). So maybe let's just go with the above instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants